package org.lop.modules.web;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * 本地静态内容使用, 对限制格式和大小的文件, 进行Gzip操作的Servlet.<br>
 * 配置web.xml:
 * 
 * <pre>
 * &lt;servlet&gt;
 * 	&lt;servlet-name&gt;staticContentServlet&lt;/servlet-name&gt;
 * 	&lt;servlet-class&gt;org.lop.modules.web.StaticContentServlet&lt;/servlet-class&gt;
 * &lt;/servlet&gt;
 * &lt;servlet-mapping&gt;
 * 	&lt;servlet-name&gt;staticContentServlet&lt;/servlet-name&gt;
 * 	&lt;url-pattern&gt;/staticContent&lt;/url-pattern&gt;
 * &lt;/servlet-mapping&gt;
 * </pre>
 * 
 * 1. WEB-INF目录下js, css不能读取, 可通过该使用引用需要的文件.<br>
 * 2. 文件高效读取, 客户端缓存控制及Gzip压缩传输.
 * 
 * <pre>
 * eg.
 * ${ctx }/staticContent?contentPath=static/images/crawfish1.jpg
 * ${ctx }/staticContent?contentPath=static/images/crawfish2.jpg&download=true
 * </pre>
 * 
 * @author 潘瑞峥
 * @date 2013-6-5
 */
public class StaticContentServlet extends HttpServlet {

	private static final long serialVersionUID = 8930080100661518049L;

	private static Logger logger = LoggerFactory.getLogger( StaticContentServlet.class );

	/** 需要被Gzip压缩的Mime类型. */
	private static final String[] GZIP_MIME_TYPES = { "text/html", "application/xhtml+xml", "text/plain", "text/css", "text/javascript",
			"application/x-javascript", "application/json",
			// 图片类.
			"image/gif", "image/png", "image/jpeg", "image/bmp" };

	/** 需要被Gzip压缩的最小文件大小. */
	private static final int GZIP_MINI_LENGTH = 512;

	/** Mime. */
	private MimetypesFileTypeMap mimetypesFileTypeMap;

	/** Spring配置. */
	@SuppressWarnings( "unused" )
	private ApplicationContext applicationContext;

	@Override
	protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
		String contentPath = request.getParameter( "contentPath" );
		if ( StringUtils.isBlank( contentPath ) ) {
			response.sendError( HttpServletResponse.SC_BAD_REQUEST, "contentPath parameter is required." );
			return;
		}

		// 获取请求内容的基本信息.
		ContentInfo contentInfo = null;
		try {
			contentInfo = this.getContentInfo( contentPath );
		} catch ( FileNotFoundException e ) {
			logger.warn( e.getMessage() );
			return;
		}

		// 根据Etag或ModifiedSince Header判断客户端的缓存文件是否有效, 如仍有效则设置返回码为304, 直接返回.
		if ( !Servlets.checkIfModifiedSince( request, response, contentInfo.lastModified )
				|| !Servlets.checkIfNoneMatchEtag( request, response, contentInfo.etag ) ) {
			return;
		}

		// 设置过期时间.
		Servlets.setExpiresHeader( response, Servlets.ONE_YEAR_SECONDS );
		// 设置最后修改时间.
		Servlets.setLastModifiedHeader( response, contentInfo.lastModified );
		Servlets.setEtag( response, contentInfo.etag );

		// 设置MIME类型.
		response.setContentType( contentInfo.mimeType );

		// 设置弹出下载文件请求窗口的Header.
		if ( null != request.getParameter( "download" ) ) {
			Servlets.setFileDownloadHeader( response, contentInfo.fileName );
		}

		// 构造OutputStream.
		OutputStream output = null;
		try {
			/* 需要gzip. */
			if ( Servlets.checkAccetptGzip( request ) && contentInfo.needGzip ) {
				// 使用压缩传输的outputstream, 使用http1.1 trunked编码不设置content-length.
				output = Servlets.buildGzipOutputStream( response );
			} else {
				// 使用普通outputstream, 设置content-length.
				response.setContentLength( contentInfo.length );
				output = response.getOutputStream();
			}

			// 高效读取文件内容并输出, 然后关闭input file.
			FileUtils.copyFile( contentInfo.file, output );
			output.flush();
		} catch ( IOException e ) {
			throw e;
		} finally {
			IOUtils.closeQuietly( output );
		}
	}

	/**
	 * 初始化.
	 */
	@Override
	public void init() throws ServletException {
		// 保存applicationContext以备后用.
		applicationContext = WebApplicationContextUtils.getWebApplicationContext( super.getServletContext() );

		// 初始化mimeTypes, 默认缺少css的定义.
		mimetypesFileTypeMap = new MimetypesFileTypeMap();
		mimetypesFileTypeMap.addMimeTypes( "text/css css" );
	}

	/**
	 * 创建Content基本信息.
	 */
	private ContentInfo getContentInfo( String contentPath ) throws FileNotFoundException {
		ContentInfo contentInfo = new ContentInfo();

		String realFilePath = super.getServletContext().getRealPath( contentPath );
		File file = new File( realFilePath );
		if ( !file.exists() ) {
			throw new FileNotFoundException( file.getPath() + " (系统找不到指定的文件。)" );
		}

		contentInfo.file = file;
		contentInfo.contentPath = contentPath;
		contentInfo.fileName = file.getName();
		contentInfo.length = ( int ) file.length();

		contentInfo.lastModified = file.lastModified();
		contentInfo.etag = "W/\"" + contentInfo.lastModified + "\"";

		contentInfo.mimeType = mimetypesFileTypeMap.getContentType( contentInfo.fileName );

		/* 超过需要压缩大小和属于需要压缩的类型. */
		if ( GZIP_MINI_LENGTH <= contentInfo.length && ArrayUtils.contains( GZIP_MIME_TYPES, contentInfo.mimeType ) ) {
			contentInfo.needGzip = true;
		} else {
			contentInfo.needGzip = false;
		}

		return contentInfo;
	}

	/**
	 * 定义Content的基本信息.
	 */
	static class ContentInfo {
		protected String contentPath;
		protected File file;
		protected String fileName;
		protected int length;
		protected String mimeType;
		protected long lastModified;
		protected String etag;
		protected boolean needGzip;
	}

}